import breve
import random
import time
import math

total_bots = 5
bots_to_exit = 5
immobiles = 10

class Controller(breve.Control):
    def __init__(self):
        breve.Control.__init__(self)
        self.setBackgroundColor(breve.vector())
        self.enableLighting()
        self.env = Room(breve.vector(20, 0, 20), 8, [], [], [84, 0])
        
    def iterate(self):
        breve.Control.iterate(self)
        self.updateNeighbors()

    def runTest(self):
        self.env.placeBots([ExitBot], [total_bots])
        # self.env.placeBots([breve.Stationary], [immobiles])
    
    def catchKeyXDown(self):
        self.runTest()
        
    def catchKeyEDown(self):
        self.signalExit()
    
    def signalExit(self):
        exitBots = self.env.fetch.some(ExitBot, bots_to_exit)
        while len(exitBots) > 0:
            exitBots.pop().leaveRoom()
    
    def catchKeyKDown(self):
        exitBots = self.env.fetch.all(VectorBot)
        while len(exitBots) > 0: breve.deleteInstance(exitBots.pop())

class ExitBot(breve.Mobile):
    def __init__(self):
        breve.Mobile.__init__(self)
        self.enablePhysics()
        self.setNeighborhoodSize(20)
        self.getOut = False
        self.following = False
        self.neighborList = []
    
    def getNeighbors(self):
        return [x for x in breve.Mobile.getNeighbors(self)
                        if not isinstance(x, Obstruction)]
    
    def iterate(self):
        breve.Mobile.iterate(self)
        self.neighborList = [x for x in self.getNeighbors() if isinstance(x, ExitBot)]
                
        if self.following:
            self.setColor(breve.vector(200,0,0))
            self.follow()
        elif len(self.neighborList) > 0 and self.getOut:
            self.start_following()
        elif self.getOut:
            self.setColor(breve.vector(0,200,200))
            self.circle()
        else:
            self.setColor(breve.vector(0,0,200))
            self.wander()
            
        self.setVelocity(breve.vector(self.getVelocity()[0], 0, self.getVelocity()[2]))
    
    def follow(self):
        self.setVelocity( (self.following.getLocation() - self.getLocation()).normalize()*15 )
        
    def start_following(self):
        temp = [x for x in self.neighborList if x.getOut]
        if len(temp) > 0:
            self.following = temp[0]
            self.getOut = False
            self.follow()
        else:
            self.circle()
    
    def circle(self):
        angle = 3.14/80
        x1 = self.getVelocity()[0]
        y1 = self.getVelocity()[2]
        
        x2 = x1*math.cos(angle) - y1*math.sin(angle)
        y2 = y1*math.cos(angle) + x1*math.sin(angle)

        self.setVelocity(breve.vector(x2, 0, y2).normalize() * 20)
            
    def wander(self):
        self.setVelocity(self.getVelocity() + (breve.randomExpression(
                breve.vector(2, 0, 2)) - breve.vector(1, 0, 1)).normalize())
    
    def avoid(self):
        """Avoids all bots, but not the exit location"""
        avoid_vector = breve.vector(0, 0, 0)
        for bot in self.neighborList:
            if not isinstance(bot, BlackHole):
                length = (self.getLocation() - bot.getLocation()).length()
                if length != 0:
                    heading = (self.getLocation() - bot.getLocation()).normalize()              
                    force = 30/length
                    avoid_vector = avoid_vector + heading*force
                
        self.setVelocity(avoid_vector)
    
    def avoid_exit(self):
        """Avoids all bots and the exit location"""
        avoid_vector = breve.vector(0, 0, 0)
        for bot in self.neighborList:
            length = (self.getLocation() - bot.getLocation()).length()
            if length != 0:
                heading = (self.getLocation() - bot.getLocation()).normalize()              
                force = 30/length
                avoid_vector = avoid_vector + heading*force
                
        self.setVelocity(avoid_vector)
    
    def leaveRoom(self):    
        self.getOut = True
        self.setColor(breve.randomExpression(
            breve.vector(.9, 0, 0)) + breve.vector(.1, 0, 0))
                    
class Room(breve.Real):
    def __init__(self, dim, unit, botTypes, botCounts, exit):
        breve.Real.__init__(self)
        self.unit = unit
        self.width = int(dim[0])
        self.height = int(dim[2])
        self.exit = breve.vector(exit[0], unit / 2, -exit[1])
        self.getController().pointCamera(
            breve.vector(self.width * unit / 2, 0, -self.height * unit / 2), 
            breve.vector(0, 200, self.height * unit))
        
        for x in range(0, self.width):
            Wall(breve.vector(x * unit + unit / 2, unit / 2, 0), 
                 breve.vector(unit, unit, .1))
            Wall(breve.vector(x * unit + unit / 2, unit / 2, -self.height * unit), 
                 breve.vector(unit, unit, .1))
        
        for z in range(0, self.height):
            Wall(breve.vector(0, unit / 2, -z * unit - unit / 2), 
                 breve.vector(.1, unit, unit))
            Wall(breve.vector(self.width * unit, unit / 2, -z * unit - unit / 2), 
                 breve.vector(.1, unit, unit))
                
        Ground(breve.vector((self.width / 2) * unit, 0, -(self.height / 2) * unit), 
                       breve.vector(self.width * unit, .1, self.height * unit)) 
        
        botCount = 0
        for c in range(0, len(botCounts)):
            botCount += botCounts[c]
        
        if botCount > self.width * self.height:
            print "Too many bots!"
        self.placeBots(botTypes, botCounts)
        
#        BlackHole(self.exit, breve.vector(unit, unit, .1))
        self.fetch = Fetcher(
            breve.vector(self.width * unit / 2, -unit / 2, 
                        -self.height * unit / 2), 
            max(self.width, self.height) * unit)

    def placeBots(self, botTypes, botCounts):
        positions = []
        positionCount = self.width * self.height
        
        for i in range(0, positionCount):
            positions.append(True)

        while len(botCounts) > 0:
            while True:
                spot = random.randint(0, positionCount - 1)
                if positions[spot]: break
                
            positions[spot] = False 
            bot = botTypes[0]()
            bot.move(breve.vector(
                int(spot % self.height) * self.unit + self.unit / 2, self.unit / 2, 
               -int(spot / self.height) * self.unit - self.unit / 2))
            bot.unit = self.unit
            bot.setShape(breve.createInstances(breve.Cube, 1).initWith(
                breve.vector(self.unit - 1, self.unit - 1, self.unit - 1)))
            
            if isinstance(bot, ExitBot):
                bot.exit = self.exit
                bot.setShape(breve.createInstances(
                    breve.Sphere, 1).initWith(self.unit / 2 - 1))
            
            botCounts[0] -= 1
            if botCounts[0] == 0:
                botCounts.pop(0)
                botTypes.pop(0)
    

class Obstruction(breve.Stationary):
    def __init__(self, position, dimension, color):
        breve.Stationary.__init__(self)
        self.setTexture(-1)
        self.setShape(breve.createInstances(breve.Cube, 1).initWith(dimension))
        self.setColor(color)
        self.move(position)
    
class Wall(Obstruction):
    def __init__(self, position, dimension):
        Obstruction.__init__(self, position, dimension, breve.vector(1.0, 1.0, 1.0))
        #self.setBitmapTransparency(0.5)    
        
class Ground(Obstruction):
    def __init__(self, position, dimension):
        Obstruction.__init__(self, position, dimension, breve.vector(0.0, 0.3, 0.0))
        #self.setBitmapTransparency(0.5)
        
class BlackHole(Obstruction):
    def __init__(self, position, dimension):
        Obstruction.__init__(self, position, dimension, breve.vector(0, 0, 0))
        self.setNeighborhoodSize(4)
        
    def iterate(self):
        self.neighborList = self.getNeighbors()
        for x in self.neighborList:
            if isinstance(x, ExitBot) and x.exit:
                breve.deleteInstance(self.neighborList.pop())

class Fetcher(breve.Mobile):
    def __init__(self, position, sight):
        breve.Mobile.__init__(self)
        self.move(position)
        self.setNeighborhoodSize(sight)
    
    def iterate(self):
        breve.Mobile.iterate(self)
        self.neighborList = self.getNeighbors()
    
    def all(self, instance):
        return self.some(instance, len(self.neighborList))
        
    def some(self, instance, count):
        instances = []
        while len(self.neighborList) > 0 and count > 0:
            bot = self.neighborList.pop()
            if isinstance(bot, instance):
                instances.append(bot)
                count -= 1
        return instances
        
Controller()